namespace OneWorks.Utils.FluentData
{
    using System;
    using System.Configuration;
    using System.Data;
    using System.Dynamic;

    public partial class DbContext : IDbContext
    {
        public DbContextData Data { get; private set; }

        public DbContext()
        {
            this.Data = new DbContextData();
        }

        internal void CloseSharedConnection()
        {
            if (this.Data.Connection == null)
                return;

            if (this.Data.UseTransaction
                && this.Data.Transaction != null)
                this.Rollback();

            this.Data.Connection.Close();

            if (this.Data.OnConnectionClosed != null)
                this.Data.OnConnectionClosed(new ConnectionEventArgs(this.Data.Connection));
        }

        public void Dispose()
        {
            this.CloseSharedConnection();
        }
    }

    public partial class DbContext
    {
        public IDbContext OnConnectionOpening(Action<ConnectionEventArgs> action)
        {
            this.Data.OnConnectionOpening = action;
            return this;
        }

        public IDbContext OnConnectionOpened(Action<ConnectionEventArgs> action)
        {
            this.Data.OnConnectionOpened = action;
            return this;
        }

        public IDbContext OnConnectionClosed(Action<ConnectionEventArgs> action)
        {
            this.Data.OnConnectionClosed = action;
            return this;
        }

        public IDbContext OnExecuting(Action<CommandEventArgs> action)
        {
            this.Data.OnExecuting = action;
            return this;
        }

        public IDbContext OnExecuted(Action<CommandEventArgs> action)
        {
            this.Data.OnExecuted = action;
            return this;
        }

        public IDbContext OnError(Action<ErrorEventArgs> action)
        {
            this.Data.OnError = action;
            return this;
        }
    }

    public partial class DbContext
    {
        public IDbContext EntityFactory(IEntityFactory entityFactory)
        {
            this.Data.EntityFactory = entityFactory;
            return this;
        }
    }

    public partial class DbContext
    {
        public IDbContext ConnectionString(string connectionString, IDbProvider dbProvider)
        {
            this.Data.ConnectionString = connectionString;
            this.Data.Provider = dbProvider;
            return this;
        }

        public IDbContext ConnectionStringName(string connectionstringName, IDbProvider dbProvider)
        {
            this.ConnectionString(this.GetConnectionStringFromConfig(connectionstringName), dbProvider);
            return this;
        }

        private string GetConnectionStringFromConfig(string connectionStringName)
        {
            var settings = ConfigurationManager.ConnectionStrings[connectionStringName];
            if (settings == null)
                throw new FluentDataException("A connectionstring with the specified name was not found in the .config file");
            return settings.ConnectionString;
        }
    }

    public partial class DbContext
    {
        public IDbContext CommandTimeout(int timeout)
        {
            this.Data.CommandTimeout = timeout;
            return this;
        }
    }

    public partial class DbContext
    {
        public ISelectBuilder<TEntity> Select<TEntity>(string sql)
        {
            return new SelectBuilder<TEntity>(this.CreateCommand).Select(sql);
        }

        public IInsertBuilder Insert(string tableName)
        {
            return new InsertBuilder(this.CreateCommand, tableName);
        }

        public IInsertBuilder<T> Insert<T>(string tableName, T item)
        {
            return new InsertBuilder<T>(this.CreateCommand, tableName, item);
        }

        public IInsertBuilderDynamic Insert(string tableName, ExpandoObject item)
        {
            return new InsertBuilderDynamic(this.CreateCommand, tableName, item);
        }

        public IUpdateBuilder Update(string tableName)
        {
            return new UpdateBuilder(this.Data.Provider, this.CreateCommand, tableName);
        }

        public IUpdateBuilder<T> Update<T>(string tableName, T item)
        {
            return new UpdateBuilder<T>(this.Data.Provider, this.CreateCommand, tableName, item);
        }

        public IUpdateBuilderDynamic Update(string tableName, ExpandoObject item)
        {
            return new UpdateBuilderDynamic(this.Data.Provider, this.CreateCommand, tableName, item);
        }

        public IDeleteBuilder Delete(string tableName)
        {
            return new DeleteBuilder(this.CreateCommand, tableName);
        }

        public IDeleteBuilder<T> Delete<T>(string tableName, T item)
        {
            return new DeleteBuilder<T>(this.CreateCommand, tableName, item);
        }

        private void VerifyStoredProcedureSupport()
        {
            if (!this.Data.Provider.SupportsStoredProcedures)
                throw new FluentDataException("The selected database does not support stored procedures.");
        }

        public IStoredProcedureBuilder StoredProcedure(string storedProcedureName)
        {
            this.VerifyStoredProcedureSupport();
            return new StoredProcedureBuilder(this.CreateCommand, storedProcedureName);
        }

        public IStoredProcedureBuilder<T> StoredProcedure<T>(string storedProcedureName, T item)
        {
            this.VerifyStoredProcedureSupport();
            return new StoredProcedureBuilder<T>(this.CreateCommand, storedProcedureName, item);
        }

        public IStoredProcedureBuilderDynamic StoredProcedure(string storedProcedureName, ExpandoObject item)
        {
            this.VerifyStoredProcedureSupport();
            return new StoredProcedureBuilderDynamic(this.CreateCommand, storedProcedureName, item);
        }
    }

    public partial class DbContext
    {
        public IDbContext IgnoreIfAutoMapFails(bool ignoreIfAutoMapFails)
        {
            this.Data.IgnoreIfAutoMapFails = true;
            return this;
        }
    }

    public partial class DbContext
    {
        private DbCommand CreateCommand
        {
            get
            {
                IDbConnection connection = null;

                if (this.Data.UseTransaction
                    || this.Data.UseSharedConnection)
                {
                    if (this.Data.Connection == null)
                    {
                        this.Data.Connection = this.Data.Provider.CreateConnection();
                        this.Data.Connection.ConnectionString = this.Data.ConnectionString;
                    }
                    connection = this.Data.Connection;
                }
                else
                {
                    connection = this.Data.Provider.CreateConnection();
                    connection.ConnectionString = this.Data.ConnectionString;
                }
                var cmd = connection.CreateCommand();
                cmd.Connection = connection;

                return new DbCommand(this, cmd);
            }
        }

        public IDbCommand Sql(string sql, params object[] parameters)
        {
            var command = this.CreateCommand.Sql(sql).Parameters(parameters);
            return command;
        }

        public IDbCommand MultiResultSql
        {
            get
            {
                var command = this.CreateCommand.UseMultiResult(true);
                return command;
            }
        }
    }

    public partial class DbContext
    {
        public IDbContext UseTransaction(bool useTransaction)
        {
            this.Data.UseTransaction = useTransaction;
            return this;
        }

        public IDbContext UseSharedConnection(bool useSharedConnection)
        {
            this.Data.UseSharedConnection = useSharedConnection;
            return this;
        }

        public IDbContext IsolationLevel(IsolationLevel isolationLevel)
        {
            this.Data.IsolationLevel = isolationLevel;
            return this;
        }

        public IDbContext Commit()
        {
            this.TransactionAction(() => this.Data.Transaction.Commit());
            return this;
        }

        public IDbContext Rollback()
        {
            this.TransactionAction(() => this.Data.Transaction.Rollback());
            return this;
        }

        private void TransactionAction(Action action)
        {
            if(this.Data.Transaction == null)
                return;
            if(!this.Data.UseTransaction)
                throw new FluentDataException("Transaction support has not been enabled.");
            action();
            this.Data.Transaction = null;
        }
    }
}